using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Security.AccessControl;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using Caliburn.Micro;
using Microsoft.Win32;
using System.Linq;
using Action = System.Action;
using MessageBox = System.Windows.MessageBox;

namespace Exones.Blend.AssetsManager
{
	[Export(typeof(IShellViewModel)), PartCreationPolicy(CreationPolicy.Shared)]
	class ShellViewModel: PropertyChangedBase, IShellViewModel
	{
		IObservableCollection<BlendFolder> installedVersions = new BindableCollection<BlendFolder>(); // installed blend versions
		private string userAssetPath; // asset path entered by the user
		private string userAssetName; // asset name entered by the user
		private bool is64Bit; // are we running 64 bit OS?
		private const string ToolboxKeyName = @"Toolbox"; // registry key name for toolbox
		private BlendFolder selectedBlendFolder; // currently selected blend folder
		private bool isAnyBlendProductInstalled = false; // do we have any Blend installed?
		private bool isError;

		private static object selectedItem = null;

		public bool IsError
		{
			get { return isError; }
			set
			{
				isError = value;
				NotifyOfPropertyChange(() => IsError);
				NotifyOfPropertyChange(() => CanInstallAssets);
			}
		}

		public bool IsAnyBlendProductInstalled
		{
			get { return isAnyBlendProductInstalled; }
			set
			{
				if (value == isAnyBlendProductInstalled)
					return;

				isAnyBlendProductInstalled = value;
				NotifyOfPropertyChange(() => IsAnyBlendProductInstalled);
			}
		}

		public object SelectedItem
		{
			get { return selectedItem; }
			private set
			{
				if (selectedItem != value)
				{
					selectedItem = value;
					NotifyOfPropertyChange(() => SelectedItem);	
				}
			}
		}		

		private bool isSelected;
		public bool IsSelected
		{
			get { return isSelected; }
			set
			{
				if (isSelected != value)
				{
					isSelected = value;
					NotifyOfPropertyChange(() => IsSelected);
					if (isSelected)
					{
						SelectedItem = this;
					}
				}
			}
		}		

		public BlendFolder SelectedBlendFolder
		{
			get { return selectedBlendFolder; }
			set
			{
				selectedBlendFolder = value;
				NotifyOfPropertyChange(() => SelectedBlendFolder);
			}
		}

		/// <summary>
		/// Get Blend root path in Windows Registry.
		/// </summary>
		public string BlendRoot
		{
			get
			{
				if (is64Bit)
				{
					return @"SOFTWARE\Wow6432Node\Microsoft\Expression\Blend";
				}

				return @"SOFTWARE\Microsoft\Expression\Blend";
			}
		}

		public IObservableCollection<BlendFolder> InstalledVersions
		{
			get { return installedVersions; }
		}

		public bool Is64Bit
		{
			get { return is64Bit; }
			set
			{
				if (value == is64Bit)
					return;

				is64Bit = value;
				NotifyOfPropertyChange(() => Is64Bit);
				InitializeInstalledVersions();
			}
		}

		public string UserAssetName
		{
			get { return userAssetName; }
			set
			{
				userAssetName = value;
				NotifyOfPropertyChange(() => UserAssetName);
				NotifyOfPropertyChange(() => CanInstallAssets);
			}
		}

		public void SelectAssetPath()
		{
			var dialog = new FolderBrowserDialog
				{
					ShowNewFolderButton = true,
					Description = "Select assets location:",
				};

			if (Directory.Exists(UserAssetPath))
			{
				dialog.SelectedPath = UserAssetPath;
			}

			if (dialog.ShowDialog() == DialogResult.OK)
			{
				UserAssetPath = dialog.SelectedPath;
			}
		}

		public string UserAssetPath
		{
			get { return userAssetPath; }
			set
			{
				userAssetPath = value;
				NotifyOfPropertyChange(() => UserAssetPath);
				NotifyOfPropertyChange(() => CanInstallAssets);
			}
		}

		private void InitializeInstalledVersions()
		{
			Action noVersionsInstalled = () =>
			{
				// fake folder with no installed text
				var blendFolder = new BlendFolder("<No Blend products found>", Enumerable.Empty<BlendFolder>()) { IsSelectable = false };

				installedVersions.Add(blendFolder);
				IsAnyBlendProductInstalled = false;
				MessageBox.Show(/*App.Current.MainWindow, */"No Blend products found, you will not be able to manage assets.",
				                "Blend Assets Manager", MessageBoxButton.OK, MessageBoxImage.Information);
			};

			try
			{
				var root = BlendRoot;

				installedVersions.Clear();
				var blendRoot = Registry.LocalMachine.OpenSubKey(root);


				if (blendRoot == null)
				{
					noVersionsInstalled();
					return;
				}

				var blendVersions = blendRoot // select blend versions keys
					.GetSubKeyNames()
					.Where(keyName => keyName != null && Regex.IsMatch(keyName, @"^\d+\.\d+$"));

				List<BlendFolder> versionFolders = new List<BlendFolder>();

				//throw new Exception("very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long very long ");

				foreach (var versionName in blendVersions)
				{
					var toolboxKey = blendRoot.OpenSubKey(string.Format(@"{0}\{1}", versionName, ToolboxKeyName), false);

					if (toolboxKey == null)
					{
						continue; // skip the version if no toolbox key found
					}

					var folders = ExtractAssetFolders(toolboxKey, 2); // extract subfolders of this key

					var versionFolder = new BlendFolder("Blend " + versionName, folders);

					versionFolders.Add(versionFolder);
				}

				if (versionFolders.Count == 0)
				{
					noVersionsInstalled(); // no blen products installed
				}
				else
				{
					var rootFolder = new BlendFolder("All versions and platforms", versionFolders); // root node to allow selecting and deselecting the whole tree
					installedVersions.Add(rootFolder);
					IsAnyBlendProductInstalled = true;
				}
			}
			catch (Exception exception)
			{
				IsAnyBlendProductInstalled = false;
				IsError = true;
				installedVersions.Clear();

				var errorRootFolder = new BlendFolder("<Unable to load Blend assets>")
					{ IsSelectable = false, IsExpanded = true };
				installedVersions.Add(errorRootFolder);
				MessageBox.Show(
					App.Current.MainWindow,
					string.Format("Unable to load Blend assets. Try running application as Administrator.\n\nError message:\n{0}.", exception.Message),
					"Error loading Blend assets",
					MessageBoxButton.OK,
					MessageBoxImage.Error);
			}
		}

		private IEnumerable<BlendFolder> ExtractAssetFolders(RegistryKey rootKey, int maxLevel, int level = 0)
		{
			var childKeys = rootKey
				.GetSubKeyNames();

			List<BlendFolder> folders = new List<BlendFolder>();
			if (level == maxLevel || childKeys.Length == 0) // leaf
			{
				foreach (var assetName in childKeys)
				{
					var assetKey = rootKey.OpenSubKey(assetName, false);

					if (assetKey == null)
					{
						continue;
					}

					var assetPath = assetKey.GetValue("") as string;
					if (assetPath == null)
					{
						continue;
					}

					var newFolder = new BlendAssetFolder(assetName, assetPath);
					folders.Add(newFolder);
				}

			}
			else
			{
				foreach (var assetName in childKeys)
				{
					var newRootKey = rootKey.OpenSubKey(assetName, false);
					var childFolders = ExtractAssetFolders(newRootKey, maxLevel, level + 1);

					var newFolder = new BlendFolder(assetName, childFolders);

					if (level == maxLevel - 1)
					{
						newFolder.IsAssetsHolder = true;
					}
					newFolder.RegistryPath = newRootKey.Name.Remove(0, newRootKey.Name.IndexOf("\\") + 1);

					folders.Add(newFolder);
				}
			}

			return folders;
		}

		public bool CanInstallAssets
		{
			get
			{
				return !string.IsNullOrWhiteSpace(UserAssetName) && Directory.Exists(UserAssetPath) && IsAnyBlendProductInstalled && !IsError &&
					installedVersions
					.First()
					.TraverseChildren()
					.Any(folder => folder.IsAssetsHolder && folder.IsChecked == true);
			}
		}

		public void ExpandAll()
		{
			installedVersions
				.First()
				.TraverseChildren()
				.Concat(new[] { installedVersions.First() })
				.ToList()
				.ForEach(folder => folder.IsExpanded = true);
		}

		public void CollapseAll()
		{
			installedVersions
				.First()
				.TraverseChildren()
				.Concat(new [] {installedVersions.First()})
				.ToList()
				.ForEach(folder => folder.IsExpanded = false);
		}

		public void InstallAssets()
		{
			var res = MessageBox.Show(App.Current.MainWindow, "Are you sure you want to add assets for selected platforms and versions?", "Installing assets", MessageBoxButton.YesNoCancel,
			                MessageBoxImage.Question);

			if (res != MessageBoxResult.Yes)
			{
				return;
			}

			var rootFolder = installedVersions.First();

			var toInstall = rootFolder
				.TraverseChildren()
				.Where(folder => folder.IsAssetsHolder && folder.IsChecked == true);

			foreach (var folder in toInstall)
			{
				try
				{
					InstallAssetForFolder(folder);
				}
				catch (Exception exc)
				{
					MessageBox.Show(App.Current.MainWindow,
					                string.Format("Couldn't install asset for {0}. Try running application as Administrator.\n\nError:\n{1}.", folder.RegistryPath, exc.Message),
									"Error installing asset",
									MessageBoxButton.OK,
									MessageBoxImage.Error);
					break;
				}
			}
		}

		private void InstallAssetForFolder(BlendFolder folder)
		{
			var key = Registry.LocalMachine.OpenSubKey(folder.RegistryPath, true);
			if (key == null)
			{
				return;
			}

			var assetNames = key.GetSubKeyNames();

			RegistryKey assetKey;
			BlendAssetFolder assetFolder;
			if (assetNames.Contains(UserAssetName))
			{
				assetKey = key.OpenSubKey(UserAssetName, true);
				assetKey.SetValue("", UserAssetPath);
				assetFolder = (BlendAssetFolder)folder.SubFolders.FirstOrDefault(blendFolder => blendFolder.Name == UserAssetName);
				assetFolder.Path = UserAssetPath;
			}
			else
			{
				assetKey = key.CreateSubKey(UserAssetName);
				assetKey.SetValue("", UserAssetPath);
				assetFolder = new BlendAssetFolder(UserAssetName, UserAssetPath);
				assetFolder.ParentFolder = folder;
				folder.SubFolders.Add(assetFolder);
			}
		}

		public ShellViewModel()
		{
			InitializeBlendRoot();
			InitializeInstalledVersions();
		}

		private void InitializeBlendRoot()
		{
			is64Bit = Environment.Is64BitOperatingSystem;			
		}
	}
}